﻿using System.IO;
using System.Text;
using System;
using System.Collections.Generic;
using Facet.Combinatorics;

namespace AdBlockListToTPLConverter
{
    /// <summary>
    /// Class for converting Firefox's AdBlock Filter List to TPL (Tracking Protection List) for IE9.
    /// </summary>
    public static class AdBlockConverter
    {
        public static void Convert(string pathToAdBlockList, string pathToConvertedList)
        {
            using (FileStream adBlockListFile = new FileStream(pathToAdBlockList, FileMode.Open))
            using (FileStream tplFile = new FileStream(pathToConvertedList, FileMode.Create))
            {
                Convert(adBlockListFile, tplFile);
            }
        }

        public static void Convert(Stream input, Stream output)
        {
            var writer = new StreamWriter(output, Encoding.UTF8);
            using (StreamReader streamReader = new StreamReader(input))
            {
                writer.WriteLine("msFilterList"); //TPL header

                CheckAdBlockPlusHeader(writer, streamReader);

                string line;
                while ((line = streamReader.ReadLine()) != null)
                {
                    IList<string> convertedLines = ConvertLine(line);

                    if (convertedLines == null)
                        continue;
                    else
                    {
                        foreach (string convertedLine in convertedLines)
                        {
                            writer.WriteLine(convertedLine);
                        }
                    }
                }
            }
            writer.Flush();
        }

        private static void CheckAdBlockPlusHeader(StreamWriter writer, StreamReader streamReader)
        {
            string line = streamReader.ReadLine();
            if (line == null)
                return;

            if (!line.StartsWith("[Adblock Plus"))
            {
                IList<string> convertedLines = ConvertLine(line);
                if (convertedLines != null)
                {
                    foreach (string convertedLine in convertedLines)
                    {
                        writer.WriteLine(convertedLine);
                    }
                }
            }
        }

        private static IList<string> ConvertLine(string adBlockLine)
        {
            if (IsAdBlockPlusComment(adBlockLine))
                return null;

            if (IsElementHidingRule(adBlockLine))
                return null;    //Tracking Protecton doesn't support element hiding rules


            bool isExceptionRule = RemoveCharactersForExceptionRule(ref adBlockLine);

            //we have to remove options before removing characters for matching begging and ending
            adBlockLine = RemoveFilterOptions(adBlockLine); 

            adBlockLine = RemoveCharsForBeginningEndMatching(adBlockLine);            

            IList<string> convertedLines = ConvertSeparatorCharacters(adBlockLine);

            if (isExceptionRule)
            {
                for (int i = 0; i < convertedLines.Count; i++)
                {
                    convertedLines[i] = "+ " + convertedLines[i];
                }
            }
            else
            {
                for (int i = 0; i < convertedLines.Count; i++)
                {
                    convertedLines[i] = "- " + convertedLines[i];   //TPL block rule
                }
            }

            return convertedLines;
        }

        private static bool IsAdBlockPlusComment(string adBlockLine)
        {
            return adBlockLine.StartsWith("!");
        }

        private static bool IsElementHidingRule(string adBlockLine)
        {
            return adBlockLine.Contains("#");
        }

        private static bool RemoveCharactersForExceptionRule(ref string adBlockLine)
        {
            bool isExceptionRule = false;
            if (adBlockLine.StartsWith("@@"))
            {
                adBlockLine = adBlockLine.Remove(0, 2);
                isExceptionRule = true;
            }
            return isExceptionRule;
        }

        private static string RemoveCharsForBeginningEndMatching(string adBlockLine)
        {
            if (adBlockLine.StartsWith("||"))
            {
                adBlockLine = adBlockLine.Remove(0, 2);
            }
            if (adBlockLine.StartsWith("|"))
            {
                adBlockLine = adBlockLine.Remove(0, 1);
            }
            if (adBlockLine.EndsWith("|"))
            {
                adBlockLine = adBlockLine.Remove(adBlockLine.Length - 1);
            }
            return adBlockLine;
        }

        private static string RemoveFilterOptions(string adBlockLine)
        {
            int indexBeginningOfOptions = adBlockLine.IndexOf('$');
            if (indexBeginningOfOptions != -1)
            {
                adBlockLine = adBlockLine.Remove(indexBeginningOfOptions);
            }
            return adBlockLine;
        }

        private static IList<string> ConvertSeparatorCharacters(string adBlockLine)
        {
            char[] realSeparators = { ':', '/', '?', '=', '&' };

            List<int> sepIndexes = GetListOfIndexesForSeparator(adBlockLine);
            if (sepIndexes.Count == 0)  //if there aren't any separator characters ('^')
                return new List<string> { adBlockLine }; //we return list with input string

            //in AdBlock Plus the end of the address is also accepted as separator
            //but in TPL we can't match to  end, so we will remove this
            //character if this situation occurs (in string and in index list)
            adBlockLine = RemoveSeparatorCharacterAtEnd(adBlockLine, sepIndexes);
            if (sepIndexes.Count == 0)
                return new List<string> { adBlockLine };

            IList<string> changedLines = ReplaceSeparatorCharacterWithRealSeparators(adBlockLine, realSeparators, sepIndexes);

            return changedLines;

        }

        private static List<int> GetListOfIndexesForSeparator(string adBlockLine)
        {
            List<int> sepIndexes = new List<int>();
            int index = 0;
            while ((index = adBlockLine.IndexOf('^', index)) != -1)
            {
                sepIndexes.Add(index);
                ++index;
            }
            return sepIndexes;
        }

        private static string RemoveSeparatorCharacterAtEnd(string adBlockLine, List<int> sepIndexes)
        {
            bool isSepCharAtEnd = (sepIndexes[sepIndexes.Count - 1] == adBlockLine.Length - 1);

            if (isSepCharAtEnd)
            {
                adBlockLine = adBlockLine.Remove(sepIndexes[sepIndexes.Count - 1]);
                sepIndexes.RemoveAt(sepIndexes.Count - 1);
            }
            return adBlockLine;
        }
        private static IList<string> ReplaceSeparatorCharacterWithRealSeparators(string adBlockLine, char[] separators, List<int> sepIndexes)
        {
            int numberOfSeparators = sepIndexes.Count;
            List<string> changedLines = new List<string>();

            Variations<char> variations = new Variations<char>(separators, sepIndexes.Count, GenerateOption.WithRepetition);

            foreach (List<char> sepCharVariation in variations)
            {
                StringBuilder changedLine = new StringBuilder(adBlockLine);
                for (int i = 0; i < sepCharVariation.Count; i++)
                {
                    changedLine[sepIndexes[i]] = sepCharVariation[i];
                }
                changedLines.Add(changedLine.ToString());
            }
            return changedLines;
        }

        //public string ConvertToString(string pathToAdBlockList)
        //{
        //    using (var memoryStream = new MemoryStream())
        //    using (FileStream adBlockListFile = new FileStream(pathToAdBlockList, FileMode.Open))
        //    {
        //        Convert(adBlockListFile, memoryStream);

        //        memoryStream.Position = 0;
        //        using (var streamReader = new StreamReader(memoryStream))
        //        {
        //            return streamReader.ReadToEnd();
        //        }
        //    }
        //}

        //public string ConvertToString(Stream adBlockListStream)
        //{
        //    using (var memoryStream = new MemoryStream())
        //    {
        //        Convert(adBlockListStream, memoryStream);

        //        memoryStream.Position = 0;
        //        using (var streamReader = new StreamReader(memoryStream))
        //        {
        //            return streamReader.ReadToEnd();
        //        }
        //    }
        //}

    }
}
